package com.kkwrite.regimen.consumer.common;

import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.kkwrite.regimen.consumer.common.WXApi.ACCESS_TOKEN;

/**
 * Created by lisha on 2018/8/9 13:15.
 *
 * @author lisha
 */
@Component("accessTokenUtil")
public class AccessTokenUtil {

    private static final String ERR_CODE_KEY = "errcode";
    private final Logger logger = LoggerFactory.getLogger(AccessTokenUtil.class);
    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate redisTemplate;
    private RestTemplate restTemplate;
    private ExecutorService service;

    /**
     * 获取access_token
     * @return access_token 或 null 或 抛出异常
     */
    public synchronized String getAccessToken() {
        String token = (String) redisTemplate.opsForHash().get("access_token", "token");
        if (token == null) {
            token = getToken();
        }
        return token;
    }

    private synchronized String getToken() {
        String result;
        try {
            result = restTemplate.getForObject(ACCESS_TOKEN, String.class);
        } catch (Exception e) {
            logger.error("发送获取access_token请求异常！异常信息：" + e.getMessage(), e);
            return null;
        }
        if (StringUtils.isBlank(result)) {
            logger.error("获取access_token请求返回结果为空");
            return null;
        }
        Map map;
        try {
            map = JSON.parseObject(result, Map.class);
        } catch (Exception e) {
            logger.error("获取access_token解析微信服务器返回结果异常！异常结果：{}", result);
            logger.error("获取access_token解析微信服务器返回结果异常！异常信息：" + e.getMessage(), e);
            return null;
        }
        if (map.containsKey(ERR_CODE_KEY)) {
            logger.error("获取access_token失败！errcode：{}，errmsg：{}", map.get("errcode"), map.get("errmsg"));
            return null;
        }
        try {
            write2Redis(map);
        } catch (Exception e) {
            logger.error("获取access_token后写入redis异常！异常信息：" + e.getMessage(), e);
            return null;
        }
        return (String) map.get("access_token");
    }

    private void refresh() {
        logger.info("启动access_token定时刷新！");
        long time = 7080;
        new Timer(true).scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                logger.info("刷新一次access_token！");
                Optional.ofNullable(restTemplate.getForObject(ACCESS_TOKEN, String.class)).ifPresent(result -> {
                    if (StringUtils.isBlank(result)) {
                        return;
                    }
                    Map map = JSON.parseObject(result, Map.class);
                    if (map.containsKey(ERR_CODE_KEY)) {
                        return;
                    }
                    write2Redis(map);
                    logger.info("access_token刷新完成！");
                });
            }
        }, time * 1000, time * 1000);
    }

    @PostConstruct
    public void init() {
        restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        service = new ThreadPoolExecutor(
                1,
                1,
                5L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                new ThreadFactoryBuilder().setNameFormat("get-access_token-task-%d").setDaemon(true).build());
        service.submit(() -> logger.info("access_token：{}", getAccessToken()));
        refresh();
    }

    private synchronized void write2Redis(Map map) {
        int expires = (int) map.get("expires_in");
        redisTemplate.opsForHash().put("access_token", "token", map.get("access_token"));
        redisTemplate.opsForHash().put("access_token", "expires_in", String.valueOf(expires));
        redisTemplate.expire("access_token", expires - 30, TimeUnit.SECONDS);
    }

    @PreDestroy
    public void destroy() {
        service.shutdown();
    }

}
